package org.acm.seguin.ant;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TimeZone;
/**
* @author Ara Abrahamian (ara_e@email.com)
* @created June 21, 2001
* @version $Revision: 1.1 $
*/
public final class CVSUtil
{
private HashMap entries;
static private TimeZone tz;
static
{
tz = TimeZone.getTimeZone( "GMT" );
}
public CVSUtil()
{
entries = new HashMap();
}
public boolean isFileModified( File file )
{
CVSEntry entry = (CVSEntry)entries.get( file.toString().replace( File.separatorChar, '/' ) );
if( entry == null )//either a new file or the Entries file not loaded yet
{
//try loading the Entries file
entry = loadEntriesFileFor( file );
if( entry == null )//new file, not yet placed at cvs
{
return true;
}
}
return !entry.equalsTime( file.lastModified() );
}
private CVSEntry loadEntriesFileFor( File file )
{
File workingDirectory = file.getParentFile();
int linenum = 0;
String line = null;
BufferedReader in = null;
File entriesFile = new File( workingDirectory + "/CVS/Entries" );
if( !entriesFile.exists() )
return null;
try
{
in = new BufferedReader( new FileReader( entriesFile ) );
for( linenum = 1 ;; ++linenum )
{
try
{
line = in.readLine();
}
catch( IOException ex )
{
line = null;
break;
}
if( line == null )
break;
if( line.startsWith( "/" ) )
{
try
{
CVSEntry entry = CVSEntry.parseEntryLine( workingDirectory, line );
entries.put( entry.getFileName(), entry );
}
catch( ParseException ex )
{
System.err.println( "Bad 'Entries' line " +linenum+ ", '" +line+ "' - " + ex.getMessage() );
}
}
}
}
catch ( IOException ex )
{
in = null;
}
finally
{
if( in != null )
{
try
{
in.close();
}
catch( IOException ex )
{
}
}
}
return (CVSEntry)entries.get( file.toString().replace( File.separatorChar, '/' ) );
}
public static class CVSEntry
{
private String fileName;
private Date date;
public String getFileName()
{
return fileName;
}
private Date getDate()
{
return date;
}
public String toString()
{
return "fileName="+fileName + " date="+date;
}
public int hashCode()
{
return fileName.hashCode();
}
public boolean equals( Object src )
{
if( src instanceof CVSEntry )
return fileName.equals( ((CVSEntry)src).getFileName() );
else
return false;
}
/**
* Determines if this timestamp is considered equivalent to
* the time represented by the parameter we are passed. Note
* that we allow up to, but not including, one second of time
* difference, since Java allows millisecond time resolution
* while CVS stores second resolution timestamps. Further, we
* allow the resolution difference on either side of the second
* because we can not be sure of the rounding.
*
*/
public boolean equalsTime( long time )
{
//System.out.println("time="+time);
//System.out.println("date.getTime()="+date.getTime());
return ( date.getTime() > time ) ? ( (date.getTime() - time) < 1000 ) : ( (time - date.getTime()) < 1000 );
}
private static String parseAToken( StringTokenizer toker )
{
String token = null;
try
{
token = toker.nextToken();
}
catch (NoSuchElementException ex)
{
token = null;
}
return token;
}
public static CVSEntry parseEntryLine( File parent_dir, String parseLine ) throws ParseException
{
String token = null;
String nameToke = null;
String versionToke = null;
String conflictToke = null;
String optionsToke = null;
String tagToke = null;
StringTokenizer toker = new StringTokenizer( parseLine, "/", true );
int tokeCount = toker.countTokens();
if( tokeCount < 6 )
{
throw new ParseException("not enough tokens in entries line "
+ "(min 6, parsed " + tokeCount + ")",
0);
}
token = parseAToken(toker);//the starting slash
nameToke = parseAToken(toker);
if( nameToke == null )
{
throw new ParseException("could not parse entry name", 0);
}
else if( nameToke.equals("/") )
{
throw new ParseException("entry has an empty name", 0);
}
else
{
token = parseAToken(toker);
if( token == null ||!token.equals("/") )
throw new ParseException("could not parse version's starting slash", 0);
}
versionToke = parseAToken(toker);
if (versionToke == null)
{
throw new ParseException("out of tokens getting version field",
0);
}
else if( versionToke.equals("/") )
{
versionToke = "";
}
else
{
token = parseAToken(toker);
if( token == null || !token.equals("/") )
throw new ParseException("could not parse conflict's starting slash", 0);
}
conflictToke = parseAToken(toker);
if( conflictToke == null )
{
throw new ParseException("out of tokens getting conflict field", 0);
}
else if( conflictToke.equals("/") )
{
conflictToke = "";
}
else
{
token = parseAToken(toker);
if( token == null ||!token.equals("/") )
throw new ParseException("could not parse options' starting slash", 0);
}
optionsToke = parseAToken(toker);
if( optionsToke == null )
{
throw new ParseException("out of tokens getting options field", 0);
}
else if( optionsToke.equals("/") )
{
optionsToke = "";
}
else
{
token = parseAToken(toker);
if( token == null ||!token.equals("/") )
throw new ParseException("could not parse tag's starting slash", 0);
}
tagToke = parseAToken(toker);
if( tagToke == null || tagToke.equals("/") )
{
tagToke = "";
}
CVSEntry entry = new CVSEntry();
nameToke = parent_dir.toString() + "/" + nameToke;
entry.fileName = nameToke.replace( File.separatorChar, '/' );
entry.setTimestamp( conflictToke );
return entry;
}
public Date parseTimestamp( String source ) throws ParseException
{
SimpleDateFormat dateFormat = new SimpleDateFormat( "EEE MMM dd HH:mm:ss yyyy", Locale.US );
dateFormat.setTimeZone( CVSUtil.tz );
Date result = dateFormat.parse( source, new ParsePosition(0) );
if( result == null )
throw new ParseException( "invalid timestamp '" + source + "'", 0 );
return result;
}
public void setTimestamp(String timeStamp)
{
String tstamp = new String(timeStamp);
String conflict = null;
if( tstamp.length() < 1 )
{
this.date = null;
}
else if( tstamp.startsWith("+") )
{
// We have received a "+conflict" format, which
// typically only comes from the server.
conflict = tstamp.substring(1);
if( conflict.equals("=") )
{
// In this case, the server is indicating that the
// file is "going to be equal" once the 'Merged' handling
// is completed. To retain the "inConflict" nature of
// the entry, we will simply set the conflict to an
// empty string (not null), as the conflict will be
// set very shortly as a result of the 'Merged' handling.
//
conflict = "";
}
}
else
{
int index = tstamp.indexOf('+');
if( index < 0 )
{
this.date = null;
}
else
{
// The "timestamp+conflict" case.
// This should <em>only</em> comes from an Entries
// file, and should never come from the server.
conflict = tstamp.substring(index + 1);
tstamp = tstamp.substring(0, index);
this.date = null; // signal need to parse!
if( tstamp.equals("Result of merge") )
{
// REVIEW should we always set to conflict?
// If timestamp is empty, use the conflict...
if( (timeStamp==null || timeStamp.length()==0) && conflict.length()>0 )
{
timeStamp = conflict;
}
}
else
{
timeStamp = tstamp;
}
}
}
// If tsCache is set to null, we need to update it...
if( this.date == null && timeStamp.length() > 0 )
{
try
{
this.date = parseTimestamp(timeStamp);
}
catch (ParseException ex)
{
this.date = null;
}
}
}
}
}